#include "console_private.h"
#include "console.h"
#include "cx_typedef.h"
#include "debug.h"
#include "pt/pt-sem.h"
#include "pt/pt.h"

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include "FreeRTOS.h"
#include "task.h"
#if _WIN32
#include <conio.h>
#elif __linux__
#include <termio.h>
#include <ncurses.h>
#include <sys/signal.h>
#endif


#define LOG_TAG "[CONSOLE]"
#include "log.h"
#define PROCESS_BUF_SIZE 128
#define PRINT_BUF_SIZE 256
char process_buf[PROCESS_BUF_SIZE];
char print_buf[PRINT_BUF_SIZE];

static struct pt_sem buf_non_empty;
static struct pt_sem command_pend;
extern const console_exec_handler_t __console_exec_start[];
extern const console_exec_handler_t __console_exec_end[];

static uint16_t rece_count = 0;
static uint16_t print_count = 0;
static struct
{
    uint8_t is_echo : 1;
    uint8_t is_raw  : 1;
    uint8_t is_input_enable : 1;
}g_console_attr = {0};
int console_ioctl(int ioctl, int* param)
{
    switch(ioctl)
    {
        case CONSOLE_IOCTL_GET_ECHO_STATUS: return g_console_attr.is_echo; break;
        case CONSOLE_IOCTL_SET_ECHO_STATUS:
        {
            if(!param)
            {
                return -EINVAL;
            }
            g_console_attr.is_echo = (*param)?1:0;
            break;
        }
        case CONSOLE_IOCTL_GET_RAW_MODE_STATUS: return g_console_attr.is_raw; break;
        case CONSOLE_IOCTL_SET_RAW_MODE_STATUS:
        {
            if(!param)
            {
                return -EINVAL;
            }
            g_console_attr.is_raw = (*param)?1:0;
            break;
        }
        case CONSOLE_IOCTL_GET_INPUT_ENABLE: return g_console_attr.is_input_enable; break;
        case CONSOLE_IOCTL_SET_INPUT_ENABLE:
        {
            if(!param)
            {
                return -EINVAL;
            }
            g_console_attr.is_input_enable = (*param)?1:0;
            rece_count = 0;
            break;
        }
        default:
            break;
    }
    return 0;
}

const console_exec_handler_t* console_get_exec_from_name(const char* exec_name)
{
    const console_exec_handler_t *ret = NULL;
    console_exec_handler_foreach(seek)
    {
        if(!strcmp(seek->exec, exec_name))
        {
            ret = seek;
            break;
        }
    }
    return ret;
}

__attribute__((weak)) char getchar_from_fifo(void)
{
    char ret = EOF;
#if _WIN32
    if (kbhit())
    {
        ret = _getch();
    }
#elif __linux__
    ret = getch();
    return ret;
#else
    ret = _getch();
#endif
    return ret;
}

__attribute__((weak)) void putchar_to_fifo(int c)
{
    print_buf[print_count ++] = (char)c;
    print_count %= PRINT_BUF_SIZE;
}

static int push_char_to_buf(char c)
{
    if(c == '\b' && rece_count > 0)
    {
        rece_count--;
    }
    else {
        process_buf[rece_count++] = c;
    }
    if(rece_count >= PROCESS_BUF_SIZE)
    {
        printf("Overload\n");
        rece_count = 0;
        return -1;
    }
    return 0;
}

char *console_get_word(const char* src, char* dst)
{
    uint8_t is_starting = 1;
    while(src && *src != '\0')
    {
        if(is_starting)
        {
            if(*src == ' ')
            {
                src++;
            }
            else {
                is_starting = 0;
            }
        }
        else
        {
            if(*src == ' ')
            {
                break;
            }
            *dst++ = *src++;
        }
    }
    *dst = '\0';
    if(src && (*src == '\0'))
    {
        src = NULL;
    }
    return (char*)src;
}
static int command_process(void)
{
    char word_buf[32];
    char *src_cmd_buf = process_buf;
    int ret = -0xff;
    while(*src_cmd_buf == ' ')
    {
        src_cmd_buf++;
    }
    if(*src_cmd_buf != '\0' && *src_cmd_buf != '\r' && *src_cmd_buf != '\n')
    {
        src_cmd_buf = console_get_word(src_cmd_buf, word_buf);
        console_exec_handler_foreach(seek)
        {
            if(!strcmp(word_buf, seek->exec))
            {
                ret = seek->handler(CONSOLE_CMD_CALL, src_cmd_buf);
            }
        }
        if(ret == -0xff)
        {
            ret = -0xfe; // put no match info
        }
    }
    if(ret == -0xfe)
    {
        LOGE("No handler match");
    }
    rece_count = 0;
    return ret;
}

static PT_THREAD(receive_process_thread(struct pt *pt))
{
    static char c = EOF, reading = 0;
    PT_BEGIN(pt);
    while(1)
    {
        if(console_ioctl(CONSOLE_IOCTL_GET_INPUT_ENABLE, NULL) == 0)
        {
            PT_YIELD(pt);
        }
        if ((c = getchar_from_fifo()) == EOF)
        {
receive_finish:
            if(reading)
            {
                PT_SEM_SIGNAL(pt, &buf_non_empty);
                reading = 0;
            }
            PT_YIELD(pt);
        }
        else
        {
            reading = 1;
            putchar_to_fifo(c);
#if _WIN32
            if (c == '\r')
#elif __linux__
            if (c == '\n')
#else
            if(c == '\r')
#endif
            {
                putchar_to_fifo('\n');
                push_char_to_buf('\0');
                PT_SEM_SIGNAL(pt, &command_pend);
                goto receive_finish;
            }
            else if(c == '\b' || c == 127)
            {
                push_char_to_buf('\b');
                putchar_to_fifo(' ');
                putchar_to_fifo('\b');
            }
            else
            {
                push_char_to_buf(c);
            }
        }
    }
    PT_END(pt);
}
static PT_THREAD(print_thread(struct pt *pt))
{
    PT_BEGIN(pt);
    while(1)
    {
        PT_SEM_WAIT(pt, &buf_non_empty);
        for(int i=0; i<print_count; i++)
        {
#ifdef _WIN32
            putchar(print_buf[i]);
#elif __linux__
            addch(print_buf[i]);
            refresh();
#else
            _putch(print_buf[i]);
#endif
        }
        print_count = 0;
    }
    PT_END(pt);
}
static PT_THREAD(console_command_handler_task(struct pt *pt))
{
    PT_BEGIN(pt);
    while(1)
    {
        PT_SEM_WAIT(pt, &command_pend);
        command_process();
    }
    PT_END(pt);
}

static PT_THREAD(console_thread_driver(struct pt *pt))
{
    static struct pt rece_proces_pt, print_pt, command_process_pt;
    PT_BEGIN(pt);

    PT_SEM_INIT(&buf_non_empty, 0);
    PT_SEM_INIT(&command_pend, 0);
    PT_INIT(&rece_proces_pt);
    PT_INIT(&print_pt);
    PT_INIT(&command_process_pt);
    PT_WAIT_THREAD(pt, receive_process_thread(&rece_proces_pt)
            & print_thread(&print_pt)
            & console_command_handler_task(&command_process_pt)
            );
    
    PT_END(pt);
}


void CTRL_C_Handler()
{
    printf("restore terminal\n");
}

int console_thread_handler(void *args)
{
    struct pt driver_pt;
#ifdef __linux__
    initscr();
    cbreak();
    noecho();
    nodelay(stdscr, TRUE);
    signal(SIGINT, CTRL_C_Handler);
#endif

    PT_INIT(&driver_pt);

    while(PT_SCHEDULE(console_thread_driver(&driver_pt)))
    {
#ifdef _WIN32
    // Sleep(0);
#elif __linux__
#endif
        vTaskDelay(15);
        
    }
    return 0;
}

#include "cx_task_message_commond.h"
int callback_test_handler(const char *str)
{
    printf("cb success:%s\r\n", str);
    printf("exit cb\r\n");
    return 2;
}
CONSOLE_COMMAND_HANDLER(show_exec)
{
    const char test_str[] = "1111";
    cx_uintptr_t callback_test[] = 
    {
        (cx_uintptr_t)callback_test_handler,
        (cx_uintptr_t)1,
        (cx_uintptr_t)test_str
    };
    printf("Console V0.0.1,param input:%s\r\n", param?param:"NULL");
    printf("Avaliable cmd:\r\n");
    int cmd_count = 0;
    console_exec_handler_foreach(seek)
    {
        cmd_count++;
        printf("<<< %d\r\ncmd:%s\r\n---\r\n", cmd_count, seek->exec);
        printf("%s \r\n>>>\r\n", seek->help_info?(seek->help_info):"No help");
    }
    int cb_ret = cx_task_messaage_callback_handler((CX_MSG_CALLBACK_T*)&callback_test);
    printf("returned :%d\r\n", cb_ret);
    return 0;
}
CONSOLE_EXEC_REGISTER(show_exec, "help", "NoHelp");
